package org.NooLab.docserver.main;

import java.util.ArrayList;


import com.iciql.Db;


import org.NooLab.utilities.files.DFutils;
import org.NooLab.utilities.logging.PrintLog;
import org.NooLab.utilities.strings.StringsUtil;

import org.NooLab.docobserver.structures.DocumentObserverIntf;
import org.NooLab.docserver.FileChange;
import org.NooLab.docserver.component.WatchedFile;
import org.NooLab.docserver.storage.DocoDataBase;
import org.NooLab.itexx.storage.docserver.storage.DataBaseHandlerIntf;
import org.NooLab.itexx.storage.DbFieldRequests;
import org.NooLab.itexx.storage.docserver.storage.db.iciql.Documents;



/**
 * 
 * @author kwa
 *
 */
public class ObserverMsgHandling implements Runnable{

	//ArrayList<WatchedFile> waitingItems = new ArrayList<WatchedFile>();
	// we need additional info beyond the WatchedFile !!
	WaitingItems waitingItems = new WaitingItems(); 
	
	DocumentObserver documentObserver;
	DocoDataBase docoDataBase;
	DataBaseHandlerIntf dbHandler ;
	Db iciDb;
	
	int addingEvent=0;
	boolean isRunning=false;
	Thread obsMsgHandlThread ;
	PrintLog out;
	
	// ========================================================================	
	public ObserverMsgHandling(DocumentObserver docObsrv) {

		documentObserver = docObsrv;
		obsMsgHandlThread = new Thread(this,"obsMsgHandlThread");
		out = documentObserver.out ;
		
	}
	// ========================================================================
	
	public void start(){
	
		try {
		
			docoDataBase = documentObserver.docoDataBase;
			dbHandler = docoDataBase.getDbHandler() ;

			docoDataBase.open() ;
			iciDb = docoDataBase.getIciDb();

		} catch (Exception e) {
			e.printStackTrace();
		}
		
		obsMsgHandlThread.start() ;
	}
	
	
	@Override
	public void run() {
		int changeFlag = -99 , renamedItem ;
		int _ll = 3;
		
		
		WaitingItem wwF ;
		
		isRunning = true ;
		try{
			
			// if there is any NEW we have to wait for 5000 if there will come a second event which is DEL !!
			int w=0;
			while (isRunning){
				
				if ((waitingItems.size()>0) && (addingEvent==0)){
					
					wwF = waitingItems.get(w);
					if (wwF==null){
						w--;
						if (w<0){
							w=0;
						}
						continue;
					}
					if (wwF.getPrintFlag()<=3){
						out.print(_ll, "> noticed File : "+ wwF.getFilename());
						wwF.incPrintFlag(1) ;
					}
					
if (waitingItems.size()>=2){
	w=w+1-1;
}
					int singleNew = observationIsNewAndSingle(wwF) ;
					if ( singleNew == 1 ){
						// just in case if there is ONLY a NEW then we should wait...
						// this happens on moves between directories, because each directory is checked by its own process
						if (waitingItems.size()>(w+1)){
							w++;
						}
						continue;
					}
					if ( singleNew == 2 ){
						out.print(_ll," change = 'recent' "+ wwF.getObservation() ) ;
					}
						
					// are there 2 waiting items (DEL+NEW), which are identical except the name? -> rename in Explorer
					renamedItem = isExplorerRename(waitingItems.size());
					if (renamedItem>=0){
						// setting flags, changing references of contents in records
						// change in DB: documents . name , GUID remains unchanged
						WatchedFile rwf = waitingItems.get(renamedItem) ;
						
						updateDocumentEntry( rwf, DocumentObserverIntf._FILE_CHANGE_RENAME );
						
						rwf = waitingItems.get(renamedItem);
						waitingItems.remove(renamedItem) ;
						
						out.print(_ll," change = RENAME of "+rwf.getFilename() ) ;
						continue;
					}
					
					
					// are there 2 waiting items (DEL+NEW), which are identical except homedir/folderid ? -> move in explorer
					int movedItem = isExplorerMove(waitingItems.size());
					if (movedItem>=0){
						
						// change in DB: folder ID , GUID remains unchanged
						// setting flags, changing references of contents in records
						// change in DB: documents . name , GUID remains unchanged
						WatchedFile rwf = waitingItems.get(movedItem) ;
						
						
						updateDocumentEntry( rwf, DocumentObserverIntf._FILE_CHANGE_MOVE );
						
						// rwf = waitingItems.get(movedItem);
						waitingItems.remove(movedItem) ;

						out.print(_ll," change = MOVE of "+rwf.getFilename() ) ;
						continue;
					}
					
					if (isSingleCommon(wwF)){ //  
						
						out.print(_ll," change = "+wwF.getObservation()+" of "+wwF.getFilename() ) ;
						
						if (wwF.getInternalFlag() ==  DocumentObserverIntf._FILE_CHANGE_MOD){
							updateDocumentEntry( wwF, DocumentObserverIntf._FILE_CHANGE_MOD );
						}
						if (waitingItems.get(w).getInternalFlag() ==  DocumentObserverIntf._FILE_CHANGE_DEL){
							wwF.setPreviousLabel( wwF.getFilename()) ;
							removeDocumentEntry( wwF ); // moving to "orphanes"
						}
						if (waitingItems.get(w).getInternalFlag() ==  DocumentObserverIntf._FILE_CHANGE_NEW){
							createDocumentEntry( wwF );
						}
						
						waitingItems.remove(w);
						continue;
					}
					//
					changeFlag = waitingItems.get(w).getInternalFlag() ;
					
					
					out.delay(5);
				}else{
					out.delay(10);
				}
			}
			
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	private int observationIsNewAndSingle( WaitingItem wwF) {
		
		boolean rB=false;
		int r= 0;
		
		int wn = waitingItems.size() ;
		int n=0, m=0,d=0;
		long now = System.currentTimeMillis() ;
		
		// not a "NEW"
		if (wwF.getInternalFlag()!=DocumentObserverIntf._FILE_CHANGE_NEW){
			return 0; // false
		}
		
		// not a fresh "NEW"
		if (wwF.getInternalFlag()==DocumentObserverIntf._FILE_CHANGE_NEW){
			if (now - wwF.registerTime < 5010){
				
				// we should wait only if the file is already at least once in the db!
				Documents dbdoc = new Documents();
				r=1; // too fresh ?
				try{
					long docid = this.iciDb.from(dbdoc)
					                       .where(dbdoc.name).is(wwF.getFilename())
					                       .and(dbdoc.status).atMost(1) 
					                       .select().get(0).docid;
					// TODO: that's not correct... ???
					
				}catch(Exception e){
					r=2 ; // never seen before... so it can't be a rename/move, thus we don't have to wait...
				}
				// 
				return r; 
			}
			if (now - wwF.registerTime >= 5010){
				return 2; // ripe
			}
		}
		
		// is it single?
		for (int i=0;i<wn;i++){
			WaitingItem item = waitingItems.get(i);
			if (item.getInternalFlag() == DocumentObserverIntf._FILE_CHANGE_NEW){
				n++;
			}
			if (item.getInternalFlag() == DocumentObserverIntf._FILE_CHANGE_DEL){
				d++;
			}
			if (item.getInternalFlag() == DocumentObserverIntf._FILE_CHANGE_MOD){
				m++;
			}
		}//i->
		
		if (n>d+m){
			r=1;
		}
		
		return r;
	}

	private void createDocumentEntry(WatchedFile wF) {

		DbFieldRequests accvalues = null;
		
		try{
		
			accvalues = new DbFieldRequests(iciDb); 
		
		}catch(Exception e){
			e.printStackTrace() ;
			return;
		}

		accvalues.put("size", wF.getFilesize()) ;
		accvalues.put("folderid", wF.getFolderId()) ;
		accvalues.put("status", DocumentObserverIntf._STATUS_FILE_REGISTERED ) ;
		
		docoDataBase.insertDocumentFull("documents", wF.getFilename(), accvalues);
		
		String filename = DFutils.createPath( wF.getHomeDir(), wF.getFilename());

		FileChange fileChange = new FileChange( "NEW", filename, filename );
		fileChange.inform( documentObserver.appCallbackInstance) ;

	}
	
	private void removeDocumentEntry(WatchedFile watchedFile) {
		long docid;
		
		DbFieldRequests accvalues = null;
		
		try{
		
			accvalues = new DbFieldRequests(iciDb); 
		
		}catch(Exception e){
			e.printStackTrace() ;
			return;
		}
		
		docid = docoDataBase.getDocumentKey( "documents", watchedFile.getPreviousLabel(), watchedFile.getFolderId(), accvalues) ;

		docoDataBase.removeEntry("documents", docid) ;
		
		String filename = DFutils.createPath( watchedFile.getHomeDir(), watchedFile.getFilename());

		FileChange fileChange = new FileChange( "DEL", filename, filename );
		fileChange.inform( documentObserver.appCallbackInstance) ;

	}

	/**
	 * handling _FILE_CHANGE_RENAME or _FILE_CHANGE_MOD
	 * 
	 * @param watchedFile
	 * @param changeFlag
	 */
	private boolean updateDocumentEntry(WatchedFile watchedFile, int changeFlag) {
		 
		String str;
		boolean rB = false;
		long docid = -1;
		DbFieldRequests accvalues = null;
		
		int _mod  = DocumentObserverIntf._FILE_CHANGE_MOD;
		int _move = DocumentObserverIntf._FILE_CHANGE_MOVE;

		
		try{
		
			accvalues = new DbFieldRequests(iciDb); 
		
		}catch(Exception e){
			e.printStackTrace() ;
			return false;
		}
		
		try{
				

			if (changeFlag == DocumentObserverIntf._FILE_CHANGE_RENAME){
				str = watchedFile.getPreviousLabel() ;
				
				docid = docoDataBase.getDocumentKey( "documents", watchedFile.getPreviousLabel(), watchedFile.getFolderId(), accvalues) ;
				
				rB = docoDataBase.updateDocumentRename( "documents", docid , 
														watchedFile.getFilename(),
														changeFlag ) ;
				
				String filename = DFutils.createPath( watchedFile.getHomeDir(), watchedFile.getFilename());

				FileChange fileChange = new FileChange( "RENAME", filename, str );
				fileChange.inform( documentObserver.appCallbackInstance) ;
			} // RENAME ?
			
			if ( ((changeFlag == _mod) || (changeFlag == _move)) 
				  ){
				long size=0 ,cdate = System.currentTimeMillis();
				
				long previousFolderId = docoDataBase.getItemKey("folders", watchedFile.getPreviousLabel() , accvalues) ;
				
				
				size = DFutils.getFileSize( DFutils.createPath( watchedFile.getHomeDir(), watchedFile.getFilename()) );
				accvalues.clear() ;
				accvalues.put( "folderid", watchedFile.getFolderId() ) ;
				accvalues.put( "size", size) ;
				accvalues.put( "changedate", cdate) ;
				accvalues.put( "status", changeFlag) ;
				
				if (((previousFolderId>=0) && (changeFlag==_move)) ||
					 (changeFlag==_mod)){
					if (previousFolderId<0){
						previousFolderId = watchedFile.getFolderId();
					}
					
					docid = docoDataBase.getDocumentKey( "documents", watchedFile.getFilename(), previousFolderId, accvalues) ;
					
					if (docid>0){
						rB = docoDataBase.updateDocumentMulti( "documents", docid, accvalues) ;

						String filename = DFutils.createPath( watchedFile.getHomeDir(), watchedFile.getFilename());

						String info = "";
						if (changeFlag==_mod){
							info = "MOD";
						}
						if (changeFlag==_move){
							info = "MOVE";
						}
 						
						FileChange fileChange = new FileChange( info, filename, filename );
						fileChange.inform( documentObserver.appCallbackInstance) ;

					} // previous doc known?
				} // previous folder known?
			} // MOVE ?
		
		}catch(Exception e){
			e.printStackTrace();
		}
		return rB;
	}
	
	
	/**
	 * 
	 * MOD and simple DEL
	 * for efficient handling of MOD, Texx should keep the last version in zipped version, using the GUID, 
	 * 
	 * @return
	 */
	private boolean isSingleCommon( WatchedFile wF ) {
		boolean rB=false;
		
		if ((wF.getObservation().contentEquals("MOD")) 
				|| (wF.getInternalFlag()==DocumentObserverIntf._FILE_CHANGE_MOD)
				|| (wF.getInternalFlag()==DocumentObserverIntf._FILE_CHANGE_DEL)
				|| (wF.getInternalFlag()==DocumentObserverIntf._FILE_CHANGE_NEW) ){
			rB = true;
		}
		
		// in TEXX
		// DEL, remove buffered version named with GUID, 
		// MOD  replace buffered version
		return rB;
	}
	
	
	/**
	 * are there 2 waiting items (DEL+NEW), which are identical except the folder id 
	 * 
	 * @return
	 */
	private int isExplorerMove( int maxIndex ) {
        // change in DB: folder ID , GUID remains unchanged

		boolean rB = false;
		int detectedItem = -1;
		WaitingItem[] wFp = new WaitingItem[2];
		int moveditemIndex;
		
		// same  same name, same size, same LDate, different folderid  (1) NEW (2) DEL
		// change in DB: documents . name , GUID remains unchanged
		for (int i=0;i<maxIndex-1;i++){
			
			wFp[0]= waitingItems.get(i);
			
			for (int k=i+1;k<maxIndex;k++){

				wFp[1] = waitingItems.get(k);

				// 
				moveditemIndex = wfilesMove(wFp);
				if (moveditemIndex>=0){ // remains -1 if conditions are not fulfilled
					// remove one of the items
					// set status from NEW to RENAME for the second
					int d=-1, m=-1;
					
					if (moveditemIndex==0){
						d=1; m=0; detectedItem = i;
					}else{
						d=0; m=1; detectedItem = k;
					}
					
					detectedItem = waitingItems.getIndex(wFp[detectedItem]);

					int dd = d;
					dd = waitingItems.getIndex(wFp[d]);

					// saving the previous location, later we need that to get the docid of the doc at its previous place
					String _dir = wFp[d].getHomeDir();
					_dir = StringsUtil.replaceall(_dir, "\\", "/") ;
					waitingItems.get(detectedItem).setPreviousLabel( _dir ) ;
					
					// remove it from memory list  
					if (dd>=0){
						waitingItems.remove(dd);
					}
					
					// we keep only the one which was indicated as "NEW" == the file at the new place,
					// remove the reference to the file in its previous folder
					// later, this item of the waitingItems list will be handled accordingly
					wFp[m].setInternalFlag( DocumentObserverIntf._FILE_CHANGE_MOVE ) ;
					wFp[m].setObservation("MOVE");	
					
					rB=true;
					
					return detectedItem;
				}
				
			}// k->
		} // i->
		
		
		return detectedItem;
	}

	/**
	 * are there 2 waiting items (DEL+NEW), which are identical except the name -> move in explorer
	 * 
	 * @return
	 */
	private int isExplorerRename( int maxIndex ) {
		boolean rB = false;
		int detectedItem = -1;
		WaitingItem[] wFp = new WaitingItem[2];
		int newitem;
		
		// same folderid, same size same LDate, (1) NEW (2) DEL
		// change in DB: documents . name , GUID remains unchanged
		for (int i=0;i<maxIndex-1;i++){
			
			wFp[0]= waitingItems.get(i);
			
			for (int k=i+1;k<maxIndex;k++){

				wFp[1] = waitingItems.get(k);

				// 
				newitem = wfilesRename(wFp);
				if (newitem>=0){ // remains -1 if conditions are not fulfilled
					// remove one of the items
					// set status from NEW to RENAME for the second
					int d=-1, m=-1;
					
					if (newitem==0){
						d=1; m=0; detectedItem = i;
					}else{
						d=0; m=1; detectedItem = k;
					}
					WatchedFile rwf = waitingItems.get(d);
					rwf.setPreviousLabel( rwf.getFilename()) ;

					waitingItems.get(detectedItem).setPreviousLabel( rwf.getFilename()) ;
					// remove it from memory list and from db table ??? 
					// removeDocumentEntry( rwf ) ;	
					int dd = d;
					// generally, it is not d (only if there are only 2 changes), we first have to get the index
					dd = waitingItems.getIndex(wFp[d]);
					if (dd>=0){
						waitingItems.remove(dd);
					}
					
					wFp[m].setInternalFlag( DocumentObserverIntf._FILE_CHANGE_RENAME ) ;
					wFp[m].setObservation("REN");	
					
					rB=true;
					
					return detectedItem;
				}
				
			}// k->
		} // i->
		
		
		return detectedItem;
	}

	private int wfilesRename(WatchedFile[] item) {
		int r = -1;
		
		int _new = DocumentObserverIntf._FILE_CHANGE_NEW;
		int _del = DocumentObserverIntf._FILE_CHANGE_DEL;
		
		// same folderid, same size same LDate, (1) NEW (2) DEL
		
		if ((item[0]==null) || (item[1]==null)){
			return r;
		}
		if (
			 (item[0].getFolderId() == item[1].getFolderId() ) &&
			 (item[0].getLDate()    == item[1].getLDate() ) &&
			 (item[0].getFilesize() == item[1].getFilesize() ) &&
			 ( ( (item[0].getInternalFlag()==_new ) && (item[1].getInternalFlag()==_del) ) ||  
			   ( (item[1].getInternalFlag()==_new ) && (item[0].getInternalFlag()==_del) )
				 )
		   ){
			if (item[1].getInternalFlag()==_new ){
				r = 1;
			}else{
				r = 0;
			}
			return r;
		}
		
		return r;
	}
	
	public int wfilesMove(WatchedFile[] item){
		int r = -1;
		
		int _new = DocumentObserverIntf._FILE_CHANGE_NEW;
		int _del = DocumentObserverIntf._FILE_CHANGE_DEL;
		
		// different folderid, same name, same size same LDate, (1) NEW (2) DEL
		
		if ((item[0]==null) || (item[1]==null)){
			return r;
		}

		if (
			 (item[0].getFolderId() != item[1].getFolderId() ) &&
			 (item[0].getLDate()    == item[1].getLDate() ) &&
			 (item[0].getFilesize() == item[1].getFilesize() ) &&
			 (item[0].getFilename().contentEquals( item[1].getFilename() )) &&
			 ( ( (item[0].getInternalFlag()==_new ) && (item[1].getInternalFlag()==_del) ) ||  
			   ( (item[1].getInternalFlag()==_new ) && (item[0].getInternalFlag()==_del) )
				 )
		   ){
			if (item[1].getInternalFlag()==_new ){
				r = 1;
			}else{
				r = 0;
			}
			return r;
		}
		
		return r;
	}
	
	public void add( WatchedFile wf){
		WaitingItem wfi = new WaitingItem(wf);
		add(wfi) ;
	}
	
	public void add( WaitingItem wfi){

		
		addingEvent=1;
	
		// add only if it is not contained yet
		if (isWatchedItemKnown(wfi)==false){
			
			waitingItems.add(wfi) ;
			
			out.print(4, "number of changed files waiting for db-treatment : " + waitingItems.size() );
		}
		
		// wait a bit for allowing pairs to show up
		out.delay(50) ;
		addingEvent=0;
	}

	private boolean isWatchedItemKnown(WaitingItem wf) {
		boolean rB=false;
		
		if (waitingItems == null){
			return false;
		}
		for (int i=0;i<waitingItems.size();i++){
			if (wf.isEqual( waitingItems.get(i) )){
				rB=true;
				break;
			}
		}
		
		return rB;
	}
	
	public void stop() {
		isRunning = false;
	}

}
